--[[
  ParticleSystem class
  --------------------

TODO:

  This class provides a number of particle systems:
   The "type" field should be set to either:
    point -- emits from a single point in space - requires the "x" and "y" fields to be set
    rect  -- emits particles within a rectangle in space - requires the "rect" field to be set

  It can operate in two modes, "random" and "ordered".
   The "method" field should be set to either:
    random  -- randomly generates particles based on parameters -- does nothing with "point"
    ordered -- generates particles in order from min to max, in a cycle
               -- for "point" type, this has the effect of making a "spread"

  Additionally, "ordered" has two modes of operation (does not apply to "point" type):
    wrap -- will just wrap values when they hit max
    bounce -- will "bounce" between min and max

--]]

ParticleSystem = {}
ParticleSystem.object = nil --this must be set to the Object that will be used in the particle system
ParticleSystem.object_args = nil --the table to pass to the object constructor
ParticleSystem.object_list = nil --the ObjectList that contains the Objects in the particle system
ParticleSystem.on = false --whether or not the system is currently emitting
ParticleSystem.type = "point" --the type of particle system
ParticleSystem.method = "random" --the method in which particles are generated
ParticleSystem.ordered_mode = "wrap" -- the mode in which ordered will operate

--for point type
ParticleSystem.x = 0
ParticleSystem.y = 0

--for rect type
ParticleSystem.rect = nil
ParticleSystem.ordered_step = 1

--Update Variables
ParticleSystem.particles_per_update_min = 1 --minimum number of particles to create per call of update 
ParticleSystem.particles_per_update_max = 1 --maximum number of particles to create per call of update
ParticleSystem.update_delay = 0 -- the delay in calls of update between emittings
ParticleSystem.update_delay_current = 0 --current count -- do not touch

-- The following pertain to the emitted Objects --

ParticleSystem.angle_min = 0 --minimum angle in degrees
ParticleSystem.angle_max = 360 --maximum angle in degrees

ParticleSystem.speed_min = 0 --minimum speed
ParticleSystem.speed_max = 1 --maximum speed

ParticleSystem.life_min = 1 --minimum lifetime in updates -- set less than 1 to make infinity
ParticleSystem.life_max = 1 --maximum lifetime in updates

ParticleSystem.rotation_init_min = 0 --minimum initial rotation
ParticleSystem.rotation_init_max = 0 --maximum initial rotation

ParticleSystem.angular_velocity_min = 0 --minimum angular velocity
ParticleSystem.angular_velocity_max = 0 --maximum angular velocity

--state variables
ParticleSystem.ordered_position = 0
ParticleSystem.ordered_direction = 1


--constructor
function ParticleSystem:new(o)
  o = o or {}
  setmetatable(o, self)
  self.__index = self
  o.object_list = ObjectList:new()
  return o
end


--internal function to emit a particle
--  recommend: do not use directly
function ParticleSystem:emit(angle)
  local tobj = self.object:new(self.object_args)

  --should be random for either creation method, at least for now
  tobj.speed = (math.random() * (self.speed_max - self.speed_min)) + self.speed_min
  tobj.rotation = (math.random() * (self.rotation_init_max - self.rotation_init_min)) + self.rotation_init_min
  tobj.angular_velocity = (math.random() * (self.angular_velocity_max - self.angular_velocity_min)) + self.angular_velocity_min 
  
  if self.life_min < 1 then
    tobj.max_updates = -1
  else
    tobj.max_updates = math.random(self.life_min, self.life_max)
  end
  
  if self.method == "random" then
     tobj.heading = math.random(self.angle_min, self.angle_max)

    if self.type == "point" then
      tobj.x = self.x
      tobj.y = self.y
    elseif self.type == "rect" then
      tobj.x = math.random(self.rect.x, self.rect.x + self.rect.w)
      tobj.y = math.random(self.rect.y, self.rect.y + self.rect.h)
    end
  elseif self.method == "ordered" then
    if self.type == "point" then
      tobj.x = self.x
      tobj.y = self.y
      tobj.angle = angle
    elseif self.type == "rect" then
      tobj.angle = math.random(self.angle_min, self.angle_max)
      tobj.y = math.floor(self.ordered_position / self.rect.w)
      tobj.x = self.ordered_position % self.rect.w
    end
  end
  
  self.object_list:push_back(tobj)
end


--updates all contained Objects and emits more if it is "on"
function ParticleSystem:update(delta)
  self.object_list:update(delta)

  --handles the delay. allows for both frame-based and delta-based animation
  if self.update_delay ~= 0 then
    if delta then
      if self.update_delay_current >= self.update_delay then
        self.update_delay_current = 0
      else
        self.update_delay_current = self.update_delay_current + delta
      end
    else
      if self.update_delay_current == self.update_delay then
        self.update_delay_current = 0
      else
        self.update_delay_current = self.update_delay_current + 1
      end
    end
  end

  --emit new Objects
  if self.on == true and self.update_delay_current == 0 then
    local cur_e = 1
    local num_e = math.random(self.particles_per_update_min, self.particles_per_update_max)

    local angle_spread = 0
    if self.type == "point" and self.method == "ordered" then
      angle_spread = (self.angle_max - self.angle_min) / (num_e)
    end

    while cur_e <= num_e do
      self:emit((angle_spread * (cur_e - 1)) + self.angle_min)
      cur_e = cur_e + 1
    end
    
    if self.type == "rect" and self.method == "ordered" then
      self.ordered_position = self.ordered_position + (self.ordered_direction * self.ordered_step)
      if self.ordered_position >= self.rect.w * self.rect.h then
        if self.ordered_mode == "wrap" then
          self.ordered_position = 0
        elseif self.ordered_mod == "bounce" then
          self.ordered_direction = -1
        end
      elseif self.ordered_position <= 0 then
        self.ordered_direction = 1
      end
    end
    
  end

end

--draws all contained Objects
function ParticleSystem:draw()
  self.object_list:draw()
end
